<?php

/*
 * PHP language interface to GigaBASE
 * Created by Konstantin Knizhnik   email: <knizhnik@altavista.net>
 * for WingerCom ApS Tlf: 4594 1401 email: <info@wingercom.net>
 */

/* command codes */
define("cli_cmd_close_session", 0);
define("cli_cmd_prepare_and_execute", 1);
define("cli_cmd_execute", 2);
define("cli_cmd_get_first", 3);
define("cli_cmd_get_last", 4);
define("cli_cmd_get_next", 5);
define("cli_cmd_get_prev", 6);
define("cli_cmd_free_statement", 7);
define("cli_cmd_abort", 8);
define("cli_cmd_commit", 9);
define("cli_cmd_update", 10);
define("cli_cmd_remove", 11);
define("cli_cmd_insert", 12);
define("cli_cmd_prepare_and_insert", 13);
define("cli_cmd_describe_table", 14);
define("cli_cmd_show_tables", 15);
define("cli_cmd_login", 16);
define("cli_cmd_precommit", 17);
define("cli_cmd_skip", 18);

/* status codes */
define("cli_ok", 0);
define("cli_bad_address", -1);
define("cli_connection_refused", -2);
define("cli_database_not_found", -3);
define("cli_bad_statement", -4);
define("cli_parameter_not_found", -5);
define("cli_unbound_parameter", -6);
define("cli_column_not_found", -7);
define("cli_incompatible_type", -8);
define("cli_network_error", -9);
define("cli_runtime_error", -10);
define("cli_closed_statement", -11);
define("cli_unsupported_type", -12);
define("cli_not_found", -13);
define("cli_not_update_mode", -14);
define("cli_table_not_found", -15);
define("cli_not_all_columns_specified", -16);
define("cli_not_fetched", -17);
define("cli_already_updated", -18);
define("cli_table_already_exists", -19);
define("cli_not_implemented", -20);
define("cli_login_failed", -21);
define("cli_empty_parameter", -22);
define("cli_closed_connection", -23);

/* CLI types */
define("cli_oid",  0);
define("cli_bool", 1);
define("cli_int1", 2);
define("cli_int2", 3);
define("cli_int4", 4);
define("cli_int8", 5);
define("cli_real4", 6);
define("cli_real8", 7);
define("cli_decimal",8);
define("cli_asciiz", 9);
define("cli_pasciiz",10);
define("cli_cstring",11);
define("cli_array_of_oid",  12);
define("cli_array_of_bool", 13);
define("cli_array_of_int1", 14);
define("cli_array_of_int2", 15);
define("cli_array_of_int4", 16);
define("cli_array_of_int8", 17);
define("cli_array_of_real4",18);
define("cli_array_of_real8",19);
define("cli_array_of_decimal",20);
define("cli_array_of_string",21);
define("cli_any",           22);
define("cli_datetime",      23);
define("cli_autoincrement", 24);
define("cli_rectangle",     25);
define("cli_undefined",     26);

/* Binding mode */
define("cli_tuple_mode",  0);
define("cli_object_mode", 1);
define("cli_array_mode",  2);



class gb_reference { 
    var $oid;
    
    function gb_reference($oid) { 
	$this->oid = $oid;
    }
}

function gb_pack_int($buf, $pos, $val) { 
    $buf[$pos++] = chr(($val >> 24) & 0xFF);
    $buf[$pos++] = chr(($val >> 16) & 0xFF);
    $buf[$pos++] = chr(($val >> 8) & 0xFF);
    $buf[$pos++] = chr($val & 0xFF);
    return $pos;
}

function gb_pack_short($buf, $pos, $val) { 
    $buf[$pos++] = chr(($val >> 8) & 0xFF);
    $buf[$pos++] = chr($val & 0xFF);
    return $pos;
}

function gb_unpack_int($buf, $pos) {
    return (ord($buf[$pos]) << 24) | ((ord($buf[$pos+1]) & 0xFF) << 16)
	| ((ord($buf[$pos+2]) & 0xFF) << 8)  | (ord($buf[$pos+3]) & 0xFF);
}

function gb_unpack_short($buf, $pos) {
    return ((ord($buf[$pos]) & 0xFF) << 8)  | (ord($buf[$pos+1]) & 0xFF);
}

$gb_dump_enabled = true;

function gb_trace($msg) { 
    global $gb_dump_enabled;
    if ($gb_dump_enabled) { 
	print($msg . "\n");
    }
}

class gb_buffer { 
    var $str; 
    var $pos;
    var $len;

    function gb_buffer($cmd, $id = 0) { 
	$this->len = 12;
	$this->str = str_repeat("\0", $this->len);
	$this->pos = 0;
	$this->put_int($this->len);
	$this->put_int($cmd);
	$this->put_int($id);
    }	

    function reset($buf) { 
	$this->str = &$buf;
	$this->pos = 0;
	return $buf;
    }

    function put_byte($val) { 
	$this->put_char(chr($val));
    }

    function put_char($val) { 
	if ($this->pos + 1 > $this->len) { 
	    $this->len *= 2;
	    $this->str .= $this->str;
	}
	$this->str[$this->pos++] = $val;
    }

    function put_int($val) { 
	if ($this->pos + 4 > $this->len) { 
	    $this->len *= 2;
	    $this->str .= $this->str;
	}
	$this->pos = gb_pack_int(&$this->str, $this->pos, $val);
    }

    function put_short($val) { 
	if ($this->pos + 2 > $this->len) { 
	    $this->len *= 2;
	    $this->str .= $this->str;
	}
	$this->pos = gb_pack_short(&$this->str, $this->pos, $val);
    }


    function put_decimal($val) { 
	$this->put_asciiz((string)$val);
    }
    
    function put_asciiz($s) { 
	$n = strlen($s);
	while ($this->pos + $n + 1 > $this->len) { 
	    $this->len *= 2;
	    $this->str .= $this->str;
	}
	for ($i = 0; $i < $n; $i++) { 
	    $this->str[$this->pos++] = $s[$i];
	}	
	$this->str[$this->pos++] = "\0";
    }
    
    function put_str($val) {
	$n = strlen($val);
	while ($this->pos + $n + 5 > $this->len) { 
	    $this->len *= 2;
	    $this->str .= $this->str;
	}
	$this->pos = gb_pack_int(&$this->str, $this->pos, $n+1);
	for ($i = 0; $i < $n; $i++) { 
	    $this->str[$this->pos++] = $val[$i];
	}
	$this->str[$this->pos++] = "\0";
    }

    function get_byte() { 
	return ord($this->str[$this->pos++]);
    }

    function get_char() { 
	return $this->str[$this->pos++];
    }

    function get_int() { 
	$this->pos += 4; 
	return gb_unpack_int(&$this->str, $this->pos - 4);
    }

    function get_short() { 
	$this->pos += 2; 
	return gb_unpack_short(&$this->str, $this->pos - 2);
    }

    function get_asciiz() { 
	for ($i = $this->pos; $this->str[$i] != "\0"; $i++);
	$start = $this->pos;
	$this->pos = $i+1;
	return substr($this->str, $start, $i - $start);
    }

    function get_decimal() {
	return (double)$this->get_asciiz();
    }

    function get_str() { 
	$n = gb_unpack_int(&$this->str, $this->pos);
	$this->pos += 4 + $n;
	return substr($this->str, $this->pos - $n, $n-1);
    }

    function end() { 
	gb_pack_int(&$this->str, 0, $this->pos);
    }

    function write_column_defs($table, $columns) { 
        $this->put_byte(sizeof($columns));
	foreach ($columns as $name => $value) {
	    $type = @$table[$name];
	    if (!is_integer($type)) { 
	        gb_trace("No such column $name");
		return cli_column_not_found;
	    }
	    switch ($type) { 
	      case cli_int8:
		$type = cli_int4;
		break;
	      case cli_real4:
	      case cli_real8:
		$type = cli_decimal;
		break;
	      case cli_array_of_real4:
	      case cli_array_of_real8:
	      case cli_array_of_int8:
		$type = cli_array_of_decimal;
		break;		
	    }
	    $this->put_byte($type);
	    $this->put_asciiz(&$name);
	}
    }

    function write_column_value($type, $val) { 
	switch ($type) { 
	  case cli_bool:
	    if (!is_bool($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for bool column");
		return cli_incompatible_type;
	    }
            $this->put_byte($val ? 1 : 0);
	    break;
	  case cli_int1:
	    if (!is_numeric($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for integer column");
		return cli_incompatible_type;
	    }
	    $this->put_byte((int)$val & 0xFF);
	    break;
	  case cli_int2:
	    if (!is_numeric($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for integer column");
		return cli_incompatible_type;
	    }
	    $this->put_short((int)$val & 0xFFFF);
	    break;
	  case cli_int4:
	    if (!is_numeric($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for integer column");
		return cli_incompatible_type;
	    }
	    $this->put_int((int)$val);
	    break;
	  case cli_oid:
	    if (get_class($val) != "gb_reference") { 
		gb_trace("Incompatible type ".gettype($val)." for reference column");
		return cli_incompatible_type;
	    }
	    $this->put_int($val->oid);
	    break;
	  case cli_real4:
	  case cli_real8:
	  case cli_decimal:
	    if (!is_numeric($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for real column");
		return cli_incompatible_type;
	    }
	    $this->put_decimal((double)$val);
	    break;		
	  case cli_asciiz:
	    $this->put_str((string)$val);
	    break;
	  case cli_array_of_bool:
	    if (!is_array($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for array of boolean column");
		return cli_incompatible_type;
	    }	    
	    $this->put_int(sizeof($val));
	    foreach ($val as $elem) { 
		$this->put_byte($elem ? 1 : 0);
	    }
	    break;
	  case cli_array_of_oid:
	    if (!is_array($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for array of reference column");
		return cli_incompatible_type;
	    }	    
	    $this->put_int(sizeof($val));
	    foreach ($val as $elem) { 
		$this->put_int($elem->oid);
	    }
	    break;
	  case cli_array_of_int1:
	    if (!is_array($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for array of integer column");
		return cli_incompatible_type;
	    }	    
	    $this->put_int(sizeof($val));
	    foreach ($val as $elem) { 
		$this->put_byte((int)$elem);
	    }
	    break;
	  case cli_array_of_int2:
	    if (!is_array($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for array of integer column");
		return cli_incompatible_type;
	    }	    
	    $this->put_int(sizeof($val));
	    foreach ($val as $elem) { 
		$this->put_short((int)$elem);
	    }
	    break;
	  case cli_array_of_int4:
	    if (!is_array($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for array of integer column");
		return cli_incompatible_type;
	    }	    
	    $this->put_int(sizeof($val));
	    foreach ($val as $elem) { 
		$this->put_int((int)$elem);
	    }
	    break;
	  case cli_array_of_real4:
	  case cli_array_of_real8:
	  case cli_array_of_decimal:
	    if (!is_array($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for array of real column");
		return cli_incompatible_type;
	    }	    
	    $this->put_int(sizeof($val));
	    foreach ($val as $elem) { 
		$this->put_decimal((double)$elem);
	    }
	    break;
	  case cli_array_of_string:
	    if (!is_array($val)) { 
		gb_trace("Incompatible type ".gettype($val)." for array of string column");
		return cli_incompatible_type;
	    }	    
	    $this->put_int(sizeof($val));
	    foreach ($val as $elem) { 
		$this->put_asciiz((string)$elem);
	    }
	    break;
	  default:
	    gb_trace("Unsupported type $type of column");
	    return cli_unsupported_type;
	}
	return cli_ok;
    }

    function get_column_value() { 
	$type = $this->get_byte();
	switch ($type) { 
	  case cli_bool:
	    return $this->get_char() != "\0";
	  case cli_int1:
	    return $this->get_byte();
	  case cli_int2:
	    return $this->get_short();
	  case cli_int4:
	    return $this->get_int();
	  case cli_oid:
	    return new gb_reference($this->get_int());
	  case cli_asciiz:
	    return $this->get_str();
	  case cli_decimal:
	    return $this->get_decimal();
	  case cli_array_of_oid:
	    $n = $this->get_int();
	    $arr = array();
	    while(--$n >= 0) { 
		$arr[] = new gb_reference($this->get_int());
	    }
	    return $arr;
	  case cli_array_of_bool:
	    $n = $this->get_int();
	    $arr = array();
	    while(--$n >= 0) { 
		$arr[] = $this->get_byte() != 0;
	    }
	    return $arr;
	  case cli_array_of_int1:
	    $n = $this->get_int();
	    $arr = array();
	    while(--$n >= 0) { 
		$arr[] = $this->get_byte();
	    }
	    return $arr;
	  case cli_array_of_int2:
	    $n = $this->get_int();
	    $arr = array();
	    while(--$n >= 0) { 
		$arr[] = $this->get_short();
	    }
	    return $arr;
	  case cli_array_of_int4:
	    $n = $this->get_int();
	    $arr = array();
	    while(--$n >= 0) { 
		$arr[] = $this->get_int();
	    }
	    return $arr;
	  case cli_array_of_decimal:
	    $n = $this->get_int();
	    $arr = array();
	    while(--$n >= 0) { 
		$arr[] = $this->get_decimal();
	    }
	    return $arr;
	  case cli_array_of_string:
	    $n = $this->get_int();
	    $arr = array();
	    while(--$n >= 0) { 
		$arr[] = $this->get_asciiz();
	    }
	    return $arr;
	  default:
	    user_error("Unsupported type $type");
	}
    }
}

class gb_connection_pool { 
    var $connection_chain;

   /*********************************************************************
    * new_connection
    *     Make new pooled connection. If there is unused connection to this host
    *     with the same user name and password
    * Parameters:
    *     host_address - string with server host name
    *     host_port    - integer number with server port
    *     user_name    - name of the user
    *     password     - password
    * Returns:
    *     pooled connection
    *********************************************************************/
    function new_connection($host_address, $host_port, $user_name = "guest", $password = "") {
        for ($con = $this->connection_chain; $con != null; $con = $con->next) {
	    if ($con->address == $host_address && $con->port == $host_port
		&& $con->user == $user_name &&	$con->password == $password)
            {
	        $this->connection_chain = $con->next;
		return $con;
	    }
	} 
	$con = new gb_connection($this);
	if ($con->open($host_address, $host_port, $user_name, $password) != cli_ok) {
	    return null;
	}
	return $con;
    }
  
   /*********************************************************************
    * release_connection
    *     Return connection to the pool
    * Returns:
    *     status code
    */
    function release_connection($conxn) {
        $conxn->next = $this->connection_chain;
	$this->connection_chain = $conxn;
	return $conxn->commit();
    }

   /*********************************************************************
    * close
    *     Physically close all opened connections
    */
    function close() { 
	for ($conxn = $this->connection_chain; $conxn != null; $conxn = $conxn->next) {
	     $conxn->pool = null;
	     $conxn->close();
        }
	$this->connection_chain = null;
    }
}

class gb_connection {
    var $fp; 
    var $table_dictionary;
    var $n_statements;
    var $opened;

    var $pool;
    var $next;
    var $address;
    var $port;
    var $user;
    var $password;
	
    function gb_connection($connection_pool = null) { 
        $this->pool = $connection_pool;
    }

   /*********************************************************************
    * cli_open
    *     Establish connection with the server
    * Parameters:
    *     host_address - string with server host name
    *     host_port    - integer number with server port
    *     user_name    - name of the user
    *     password     - password
    * Returns:
    *     status code
    */    
    function open($host_address, $host_port, $user_name = "guest", $password = "") { 
	$this->fp = fsockopen($host_address, $host_port, &$errno, &$errstr, 30);
 	if (!$this->fp) {
	    echo "$errstr ($errno)<br>\n";
	    return cli_connection_refused;
	}
	$this->address = $host_address;
	$this->port = $host_port;
	$this->user = $user_name;
	$this->password = $password;
	$req_buf = new gb_buffer(cli_cmd_login);
	$req_buf->put_asciiz($user_name);
	$req_buf->put_asciiz($password);
	$req_buf->end();
	$rc = $this->send(&$req_buf);
	if ($rc == cli_ok) { 
 	    $response = $this->receive(4);
	    if ($response == null) { 
	        return cli_network_error;
	    } 
	    $rc = gb_unpack_int(&$response, 0);
	    if ($rc == cli_ok) { 
	        $this->n_statements = 0;
	        $this->opened = true;
            }
        }
        return $rc;
    }

   /*********************************************************************
    * cli_close
    *     Close session
    * Returns:
    *     status code
    */
    function close() { 
	if (!$this->opened) { 
	    return cli_closed_connection;
	}
	if ($this->pool != null) {	     
	    return $this->pool->release_connection();
        }
	$req_buf = new gb_buffer(cli_cmd_close_session);
	$rc = $this->send(&$req_buf);
	$this->opened = false;
	fclose($this->fp);
	return $rc;
    }

   /*********************************************************************
    * cli_statement
    *     Specify SubSQL statement to be executed at server
    *     Binding to the parameters and columns can be established
    * Parameters:
    *     stmt    - string with SubSQL statement
    * Returns:
    *     created gb_statement object
    */
    function create_statement($stmt) { 	
	if (!$this->opened) { 
	    return null;
	}
	$parameters = array();
	for ($src = 0, $dst = 0, $len = strlen($stmt); $src < $len;) { 
	    $ch = $stmt[$src];
	    if ($ch == '\'') { 
		do {
		    do {
			$stmt[$dst++] = $stmt[$src++];
			if ($src == $len) { 
			    return null;
			}
		    } while ($stmt[$src] != '\'');	
		    $stmt[$dst++] = '\'';
		} while (++$src < $len && $stmt[$src] == '\'');
	    } else if ($ch == '%') { 
		$begin = $src;
		do {
		    if (++$src == $len) { 
			break;
		    }
		    $ch = $stmt[$src];
		} while (($ch >= 'a' && $ch <= 'z') || ($ch >= 'A' && $ch <= 'Z')
			 || ($ch >= '0' && $ch <= '9') || $ch == '_');
		if ($ch == '%') {
		    return null;
		}
		$name = substr($stmt, $begin, $src - $begin);
		$parameters[$name] = cli_undefined;
		$stmt[$dst++] = "\0";
	    } else { 
		$stmt[$dst++]= $stmt[$src++];
	    }
	}
	return new gb_statement($this, $parameters, substr($stmt, 0, $dst));
    }

   /*********************************************************************
    * cli_commit
    *     Commit current database transaction
    * Returns:
    *     status code
    */
    function commit() {
	if (!$this->opened) { 
	    return cli_closed_connection;
	}
	return $this->send_receive(cli_cmd_commit);
    }

   /*********************************************************************
    * cli_precommit
    *     Release all locks set by current database transaction transaction
    * Returns:
    *     status code
    */
    function precommit() {
	if (!$this->opened) { 
	    return cli_closed_connection;
	}
	return $this->send_receive(cli_cmd_precommit);
    }

   /*********************************************************************
    * cli_abort
    *      Rollback transaction
    * Returns:
    *     status code
    */
    function rollback() {
	if (!$this->opened) { 
	    return cli_closed_connection;
	}
	return $this->send_receive(cli_cmd_abort);
    }

   /*********************************************************************
    * cli_insert
    *     Insert object in the table with the same name as the object class
    * Parameters:
    *     obj - object to be inserted
    * 
    * Returns:
    *     status code
    */
    function insert($obj, $oid) {
	if (!$this->opened) { 
	    return cli_closed_connection;
	}
	$cls = get_class($obj);
	if ($cls == null) {
	    return cli_incompatible_type;
	}
	$fields = get_object_vars($obj);
	
	$rc = $this->describe_table($cls, &$table);
	if ($rc != cli_ok) { 
	    return $rc;
	}
	$buf = new gb_buffer(cli_cmd_prepare_and_insert);
	$buf->put_asciiz("insert into $cls");
	$rc = $buf->write_column_defs(&$table, &$fields);
	if ($rc != cli_ok) {
	    return $rc;
	}

	foreach ($fields as $name => $value) {
	    $rc = $buf->write_column_value($table[$name], $obj->$name);
	    if ($rc != cli_ok) { 
		return $rc;
	    }
	}
	$buf->end();
	$rc = $this->send(&$buf);
	if ($rc != cli_ok) {
	    return $rc;
	}
	if (!$buf->reset($this->receive(12))) { 
	    return cli_network_error;
	}
	$rc = $buf->get_int();
	if ($rc == cli_ok) { 
	    $rowid = $buf->get_int();
	    $oid = $buf->get_int();
	    if ($oid != 0) {
		$oid = new gb_reference($oid);
	    } else { 
		$oid = null;
	    }
	} 
	return $rc;
    }

   /*********************************************************************
    * cli_show_tables
    *     Returns information about tables present in the database
    * Parameters:
    *     tables - reference to variable to receive array table names
    * Returns:
    *     status code
    */
    function show_tables($tables) {  
	$buf = new gb_buffer(cli_cmd_show_tables);
	$rc = $this->send(&$buf);
	if ($rc != cli_ok) {
	    return $rc;
	}
	if (!$buf->reset($this->receive(8))) { 
	    return cli_network_error;
	}
	$len = $buf->get_int();
	$n_tables = $buf->get_int();
	if ($n_tables == -1) {
	    return cli_table_not_found;
	}
	$response = $this->receive($len);
	if ($response == null) { 
	    return cli_network_error;
	}
	$tables = array();
	for ($i = 0; $i < $len; $i = $j + 1) { 
	    for ($j = $i; $response[$j] != "\0"; $j++);
	    $tables[] = substr($response, $i, $j-$i);
        }
	return cli_ok;
    }

   /*********************************************************************
    * cli_describe_table
    *     Returns information about table column names and types
    * Parameters:
    *     name  - name of the table
    *     table - reference to variable to receive associative array with <name,type>
    *             pairs
    * 
    * Returns:
    *     status code
    */
    function describe_table($name, $table) {  
	$cls_desc = @$this->table_dictionary[$name];
	if ($cls_desc != null) {
	    $table = $cls_desc;
	    return cli_ok;
	}
	$buf = new gb_buffer(cli_cmd_describe_table);
	$buf->put_asciiz(&$name);
	$buf->end();
	$rc = $this->send(&$buf);
	if ($rc != cli_ok) {
	    return $rc;
	}
	if (!$buf->reset($this->receive(8))) { 
	    return cli_network_error;
	}
	$len = $buf->get_int();
	$n_columns = $buf->get_int();
	if ($n_columns == -1) {
	    return cli_table_not_found;
	}
	$response = $this->receive($len);
	if ($response == null) { 
	    return cli_network_error;
	}
	$table = array();
	for ($i = 0; $i < $len; $i = $j + 1) { 
	    $type = ord($response[$i++]);
	    $flags = ord($response[$i++]);
	    for ($j = $i; $response[$j] != "\0"; $j++);
	    $field_name = substr($response, $i, $j-$i);
            $i = $j + 1;
	    for ($j = $i; $response[$j] != "\0"; $j++);
            $ref_table_name = substr($response, $i, $j-$i);
            $i = $j + 1;
	    for ($j = $i; $response[$j] != "\0"; $j++);
            $inverse_ref_name = substr($response, $i, $j-$i);
	    $table[$field_name] = $type;
	    $n_columns -= 1;
	}
	$this->table_dictionary[$name] = &$table;
	return cli_ok;
    }

   /*********************************************************************
    * Internal funtions
    *********************************************************************/
    
    function send($req_buf) { 
	$rc = fwrite($this->fp, &$req_buf->str, $req_buf->pos);
	return ($rc == $req_buf->pos) ? cli_ok : cli_network_error;
    }	
    
    function receive($len) { 
	$response = null;
	do { 
	    $s = fread($this->fp, $len);
	    if ($s == null) { 
		return null;
	    }
	    if ($response == null) { 
		$response = $s;	
	    } else { 
		$response .= $s;
            }
	    $len -= strlen($s);
	} while ($len != 0);
	return $response;
    }

    function send_receive($cmd, $stmt_id=0) { 
	$buf = new gb_buffer(&$cmd, $stmt_id);
	$rc = $this->send(&$buf);
	if ($rc == cli_ok) { 
	    $response = $this->receive(4);
	    if ($response == null) { 
		return cli_network_error;
	    }
	    return gb_unpack_int(&$response, 0);
	}
	return $rc;
    }

    function extract_table($query, $keyword, $table) {
	$s = stristr($query, $keyword);
	if (!$s) { 
            gb_trace("Failed to locate keyword $keyword in query $query");
	    return cli_bad_statement;
	} 
	if (sscanf(substr($s, strlen($keyword)), "%s", &$table_name) != 1) { 
            gb_trace("Failed to extract table name from query $s");
	    return cli_bad_statement;
	} 
	return $this->describe_table(&$table_name, &$table);
    }
}


class gb_statement { 
    var $connection; 
    var $parameters;
    var $parameter_binding;
    var $columns;
    var $record;
    var $fields;
    var $table;
    var $mode;
    var $prepared;
    var $stmt_body;
    var $stmt_id;
    var $curr_oid;
    var $updated;
    var $for_update;

   /*********************************************************************
    * cli_bind_parameter
    *     Bind parameter to the statement
    * Parameters:
    *     name - string with name of the column
    *     binding - reference to variable. Variable should be alredy assigned
    *               a value or type should be specified for it.
    * Returns:
    *     status code
    */
    function bind_parameter($name, $binding) {
         if (!$this->connection) { 
            return cli_closed_statement;
        }
        $this->prepared = false;
        if (!is_integer(@$this->parameters[$name])) { 
            return cli_parameter_not_found;
        }       
        if (empty($binding)) { 
            return cli_empty_parameter;
        } 
        if (is_bool($binding)) { 
            $type = cli_bool;
        } else if (is_int($binding)) { 
            $type = cli_int4;
        } else if (is_float($binding)) { 
            $type = cli_decimal;
        } else if (is_double($binding)) { 
            $type = cli_decimal;
        } else if (is_string($binding)) { 
            $type = cli_asciiz;
        } else if (get_class($binding) == "gb_reference") { 
            $type = cli_oid;
        } else {
            gb_trace("Unsupported type $type of parameter $name");
            return cli_unsupported_type;
        }
        $this->parameters[$name] = $type;
        $this->parameter_binding[$name] = &$binding;
        return cli_ok;
    }

   /*********************************************************************
    * cli_bind_column
    *     Bind column of select or insert statement
    * Parameters:
    *     name - string with name of the column
    *     binding - reference to variable
    * Returns:
    *     status code
    */
    function bind_column($name, $binding) { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        $this->prepared = false;
        $this->mode = cli_tuple_mode;
        $this->columns[$name] = &$binding;
        return cli_ok;
    }

   /*********************************************************************
    * cli_bind_object
    *     Bind variable to receive fetched object or to specify inserted object
    * Parameters:
    *     binding - reference to variable.
    * Returns:
    *     status code
    */
    function bind_object($binding) { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        $this->prepared = false;
        $this->mode = cli_object_mode;
        $this->record = &$binding;
        return cli_ok;
    }

   /*********************************************************************
    * cli_bind_array
    *     Bind variable to receive fetched record as associative array
    * Parameters:
    *     binding - reference to variable.
    * Returns:
    *     status code
    */
    function bind_array($binding) { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        $this->prepared = false;
        $this->record = &$binding;
        $this->mode = cli_array_mode;
        return cli_ok;
    }

   /*********************************************************************
    * cli_fetch
    *     Execute select statement.
    * Parameters:
    *     for_update - not zero if fetched rows will be updated
    * Returns:
    *     >= 0 - success, for select statements number of fetched rows is returned
    *     <  0 - error code
    */
    function fetch($for_update = false) { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        if (!$this->prepared) { 
            $rc = $this->connection->extract_table(&$this->stmt_body, "from", &$this->table);
            if ($rc != cli_ok) { 
                return $rc;
            }
            if ($this->mode != cli_tuple_mode) { 
                $fetched_columns = &$this->table;
            } else { 
                $fetched_columns = &$this->columns;
            }
            $buf = new gb_buffer(cli_cmd_prepare_and_execute, $this->stmt_id);
            $buf->put_byte(sizeof($this->parameters));      
            $buf->put_byte(sizeof($fetched_columns));
            $len = strlen($this->stmt_body);
            $add_nl = false;
            if ($len == 0 || $this->stmt_body[$len-1] != "\0") { 
                $len += sizeof($this->parameters);
                $add_nl = true;
                $buf->put_short($len+1);
            } else { 
                $len += sizeof($this->parameters);
                $buf->put_short($len);
            }
            reset(&$this->parameters);
            $i = 0; 
            do { 
                $ch = $this->stmt_body[$i++];
                $buf->put_char($ch);
                $len -= 1;
                if ($ch == "\0") { 
                    if ($len != 0) { 
                        $type = current($this->parameters);
                        if ($type == cli_undefined) { 
                            gb_trace("Unbound parameter " . key($this->parameters) . "\n");
                            return cli_unbound_parameter;
                        }
                        $buf->put_byte($type);
                        $len -= 1;
                        next($this->parameters);
                    }               
                }
            } while ($len != 0);
            if ($add_nl) { 
                $buf->put_byte("\0");
            }           
            foreach ($fetched_columns as $name => $value) {
                $type = $this->table[$name];
                if ($type == cli_real4 || $type == cli_real8) { 
                    $type = cli_decimal;
                } else if ($type == cli_int8) { 
                    $type = cli_int4;
                }
                $buf->put_byte($type);
                $buf->put_asciiz(&$name);
            }
            $this->for_update = $for_update;
        } else { 
            $buf = new gb_buffer(cli_cmd_execute, $this->stmt_id);
        }
        $buf->put_byte($for_update ? 1 : 0);
        foreach ($this->parameter_binding as $name => $binding) { 
            switch ($this->parameters[$name]) { 
              case cli_bool:
                $buf->put_byte($binding ? 1 : 0);
                break;
              case cli_int4:
                $buf->put_int((int)$binding);
                break;
              case cli_decimal:
                $buf->put_decimal((double)$binding);
                break;          
              case cli_asciiz:
                $buf->put_asciiz((string)$binding);
                break;
              case cli_oid:
                $buf->put_int($binding->oid);
                break;
              default:
                user_error("Invalid type " .  $this->parameters[$name] . " for parameter " . $name);
            }
        }
        $buf->end();
        $rc = $this->connection->send(&$buf);
        if ($rc != cli_ok) {
            return $rc;
        }
        if (!$buf->reset($this->connection->receive(4))) { 
            return cli_network_error;
        }
        $result = $buf->get_int();
        if ($result >= 0) { 
            $this->prepared = true;
        }
        return $result;
    }
        
   /*********************************************************************
    * cli_fetch_objects
    *     Return array with all fetched objects 
    * Parameters:
    *     reference to array variable 
    * Returns:
    *     status code
    */
    function fetch_objects($arr) { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        $save_mode = $this->mode;
        $this->mode = cli_object_mode;
        $rc = fetch();
        if ($rc < 0) { 
            return $rc;
        }
        while ($this->get_next() == cli_ok) { 
            $arr[] = $this->record;
        }
        $this->mode = $save_mode;
        return cli_ok;
    }
        
   /*********************************************************************
    * cli_fetch_tuples
    *     Return array with all fetched tuples 
    * Parameters:
    *     reference to array variable 
    * Returns:
    *     status code
    */
    function fetch_tuples($arr) { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        $save_mode = $this->mode;
        $this->mode = cli_array_mode;
        $rc = fetch();
        if ($rc < 0) { 
            return $rc;
        }
        while ($this->get_next() == cli_ok) { 
            $arr[] = $this->record;
        }
        $this->mode = $save_mode;
        return cli_ok;
    }
        
   /*********************************************************************
    * cli_insert
    *     Execute insert statement.
    * Parameters:
    *     oid        - object identifier of created record.
    * Returns:
    *     status code
    */
    function insert($oid) { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        if (!$this->prepared) { 
            $rc = $this->connection->extract_table(&$this->stmt_body, "into", &$this->table);
            if ($rc != cli_ok) { 
                return $rc;
            }
            $buf = new gb_buffer(cli_cmd_prepare_and_insert, $this->stmt_id);
            $buf->put_asciiz(&$this->stmt_body);
            switch ($this->mode) { 
              case cli_object_mode:
                $cls = get_class($obj);
                if ($cls == null) {
                    return cli_incompatible_type;
                }
                $this->fields = get_obj_vars($obj);
                if ($this->fields == null) { 
                    return cli_incompatile_type;
                }
                $rc = $buf->write_column_defs(&$this->table, &$this->fields);
                break;
              case cli_array_mode:
                if (!is_array($this->record)) { 
                    return cli_inompatible_type;
                }
                $rc = $buf->write_column_defs(&$this->table, &$this->record);
                break;
              case cli_tuple_mode:
                $rc = $buf->write_column_defs(&$this->table, &$this->columns);
                break;
              default:
                user_error("Invalid mode: " . mode);
            }
            if ($rc != cli_ok) { 
                return $rc;
            }
        } else { 
            $buf = new gb_buffer(cli_cmd_insert, $this->stmt_id);
        }
        switch ($this->mode) { 
          case cli_object_mode:
            foreach ($this->fields as $name => $value) { 
                $rc = $buf->write_column_value($this->table[$name], $this->record->$name);
                if ($rc != cli_ok) { 
                    return $rc;
                }
            }
            break;
          case cli_tuple_mode:
            foreach ($this->columns as $name => $binding) {
                $rc = $buf->write_column_value($this->table[$name], &$binding);
                if ($rc != cli_ok) { 
                    return $rc;
                }
            }
            break;
          case cli_array_mode:
            foreach ($this->record as $name => $value) {
                $rc = $buf->write_column_value($this->table[$name], &$value);
                if ($rc != cli_ok) { 
                    return $rc;
                }
            }
            break;
          default:
            user_error("Invalid mode: " . mode);
        }
        $buf->end();
        $rc = $this->connection->send(&$buf);
        if ($rc != cli_ok) {
            return $rc;
        }
        if (!$buf->reset($this->connection->receive(12))) { 
            return cli_network_error;
        }
        $rc = $buf->get_int();
        if ($rc == cli_ok) { 
            $rowid = $buf->get_int();
            $oid = $buf->get_int();
            if ($oid != 0) {
                $oid = new gb_reference($oid);
                $this->prepared = true;
            } else { 
                $oid = null;
            }
        } 
        return $rc;
    }       
   
    function get_record($cmd, $n = 0) { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        if (!$this->prepared) {
            return cli_not_fetched;
        }
        $buf = new gb_buffer(&$cmd, $this->stmt_id);
        if ($cmd == cli_cmd_skip) { 
            $buf->put_int($n);
        }
        $rc = $this->connection->send(&$buf);
        if ($rc != cli_ok) { 
            return $rc;
        }
        if (!$buf->reset($this->connection->receive(4))) { 
            return cli_network_error;
        }
        $len = $buf->get_int();
        if ($len <= 0) { 
            return $len;
        }
        if (!$buf->reset($this->connection->receive($len-4))) { 
            return cli_network_error;
        }
        $this->curr_oid = $buf->get_int();
        if ($this->curr_oid == 0) { 
            $this->curr_oid = null;
            return cli_not_found;
        }
        switch ($this->mode) { 
          case cli_object_mode:
            foreach ($this->table as $name => $type) {
                $this->record->$name = $buf->get_column_value();
            }
            break;
          case cli_tuple_mode:
            foreach ($this->columns as $binding) {
                $binding = $buf->get_column_value();
            }
            break;
          case cli_array_mode:
            foreach ($this->table as $name => $type) {
                $this->record[$name] = $buf->get_column_value();
            }
            break;
          default:
            user_error("Invalid mode: " . mode);
        }
        $this->updated = false; 
        return cli_ok;
    }



   /*********************************************************************
    * cli_get_first
    *     Get first row of the selection.
    * Returns:
    *     status code
    */
    function get_first() { 
        return $this->get_record(cli_cmd_get_first);
    }

   /*********************************************************************
    * cli_get_last
    *     Get last row of the selection.
    * Returns:
    *     status code
    */
    function get_last() { 
        return $this->get_record(cli_cmd_get_last);
    }

   /*********************************************************************
    * cli_get_next
    *     Get next row of the selection. If get_next records is called
    *     exactly after cli_fetch function call, is will fetch the first record
    *     in selection.
    * Returns:
    *     status code
    */
    function get_next() { 
        return $this->get_record(cli_cmd_get_next);
    }


   /*********************************************************************
    * cli_get_prev
    *     Get previous row of the selection. If get_next records is called
    *     exactly after cli_fetch function call, is will fetch the last record
    *     in selection.
    * Returns:
    *     status code
    */
    function get_prev() { 
        return $this->get_record(cli_cmd_get_prev);
    }



   /*********************************************************************
    * cli_skip
    *     Skip specified number of rows. 
    * Parameters:
    *     statement  - statememt descriptor returned by cli_statement
    *     n          - number of objects to be skipped
    *                - if "n" is positive, then this function has the same effect as
    *                    executing cli_get_next() function "n" times.
    *                - if "n" is negative, then this function has the same effect as
    *                     executing cli_get_prev() function "-n" times.
    *                - if "n"  is zero, this method has no effect
    * Returns:
    *     status code
    */
    function skip($n) { 
        return $this->get_record(cli_cmd_skip, $n);
    }


   /*********************************************************************
    * cli_get_oid
    *     Get object identifier of the current record
    * Returns:
    *     object identifier or 0 if no object is seleected
    */
    function get_oid() { 
        if ($this->curr_oid) { 
            return new gb_reference($this->curr_oid);
        }
        return null;
    }

   /********************************************************************
    * cli_update
    *     Update the current row in the selection. You have to set
    *     for_update parameter of cli_fetch to 1 in order to be able
    *     to perform updates. Updated value of row fields will be taken
    *     from bound column variables.
    * Returns:
    *     status code
    */
    function update() {
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        if (!$this->prepared) {
            return cli_not_fetched;
        }
        if (!$this->curr_oid) {
            return cli_not_found;
        }
        if (!$this->for_update) {
            return cli_not_update_mode;
        }
        if ($this->updated) { 
            return cli_already_updated;
        }

        $buf = new gb_buffer(cli_cmd_update, $this->stmt_id);
        switch ($this->mode) { 
          case cli_object_mode:
            foreach ($this->fields as $name => $value) { 
                $rc = $buf->write_column_value($this->table[$name], $this->record->$name);
                if ($rc != cli_ok) { 
                    return $rc;
                }
            }
            break;
          case cli_tuple_mode:
            foreach ($this->columns as $name => $binding) {
                $rc = $buf->write_column_value($this->table[$name], &$binding);
                if ($rc != cli_ok) { 
                    return $rc;
                }
            }
            break;
          case cli_array_mode:
            foreach ($this->record as $name => $value) {
                $rc = $buf->write_column_value($this->table[$name], &$value);
                if ($rc != cli_ok) { 
                    return $rc;
                }
            }
            break;
          default:
            user_error("Invalid mode: " . mode);
        }
        $buf->end();
        $rc = $this->connection->send(&$buf);
        if ($rc != cli_ok) {
            return $rc;
        }
        $this->updated = true;
        if (!$buf->reset($this->connection->receive(4))) { 
            return cli_network_error;
        }
        return $buf->get_int();
    }

   /*********************************************************************
    * cli_remove
    *     Remove all selected records. You have to set
    *     for_update parameter of cli_fetch to 1 in order to be able
    *     to remove records.
    * Returns:
    *     status code
    */
    function remove() { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        if (!$this->prepared) {
            return cli_not_fetched;
        }
        if (!$this->for_update) {
            return cli_not_update_mode;
        }
        return $this->connection->send_receive(cli_cmd_remove, $this->stmt_id); 
    }

   /*********************************************************************
    * cli_free
    *     Deallocate statement and all associated data
    * Returns:
    *     status code
    */
    function free() { 
        if (!$this->connection) { 
            return cli_closed_statement;
        }
        $rc = $this->connection->send(new gb_buffer(cli_cmd_free_statement, $this->stmt_id));
        $this->connection = null; 
        return $rc;
    }

    function gb_statement($connection, $parameters, $stmt) { 
        $this->connection = $connection;
        $this->parameters = $parameters;
        $this->parameter_binding = array();
        $this->stmt_body = $stmt;
        $this->stmt_id = ++$this->connection->n_statements;
        $this->mode = cli_tuple_mode;
        $this->updated = false; 
        $this->columns = array();
        $this->prepared = false;
        $this->curr_oid = null;
   }
}

?>


